

//           《 Makefile 》


bin = testpipe
src = testpipe.cc

# test:test.c //# 注释 @ ==> test == bin , ^ ==> test.c == src
$(bin) : $(src)
	@g++ -o $@ $^ -std=c++11 
.PHONY:clean
clean :
	@rm - f $(bin)


//———————————————————————————————————————————————————————————————————



//           《 testpipe.cc 》



//———————————————————————————————————————————————————————————————————


//           《 testpipe.cc 》



#include <iostream>
#include <cerrno>     //errne.h   (C++版)
#include <cstring>    //string.h  (C++版)
#include <string>
#include <unistd.h>   //vscode 在 windows 环境下无法使用，安装插件Clangd
#include <sys/wait.h> 
#include <sys/types.h>

// int pipe(int pipefd[2]);  成功返回0，否则返回-1
// pipefd[0] -> r , pipefd[1] -> w ，读==[0]， 写==[1]

//比如： 定义一个全局变量： const int size = 1024;
//fork之后子进程是能拿到父进程的数据的，这是通信吗？(只能通信一次，不能一直通信，我们指的通信是要能一直通信)
// 写时拷贝，任何一方去修改这个全局变量，对方都看不到。


const int size = 1024;

//子进程向父进程发送一个可以变化的信息，比如带入子进程的 pid 计数器 等信息
std::string getOtherMessage()
{
    static int cnt = 0;         //静态变量，属于全局的变量，但是作用域在本函数内有效，但生命周期伴随程序一直存在(出了作用域不销毁)
    //计数器
    std::string messageid = std::to_string(cnt);     //to_string 把内容强转为 "字符串",     // stoi(把字符串转整数)
    cnt++;
    pid_t self_id = getpid();   //还想把 PID 也拿到
    //子进程的：pid
    std::string stringpid = std::to_string(self_id);

    std::string message = "messageid: ";
    message += messageid;       //字符串追加内容 ,字符串， pid 
    message += "  my pid is: ";
    message += stringpid;

    return message; 
}

//如何发送信息呢? 通过管道：
//管道也是文件 -> read/write
//(读/写一个文件描述符，数据放哪？缓冲区，数据大小)
//ssize_t read(int fd, void *buf, size_t count);
//ssize_t write(int fd, const void *buf, size_t count);


// // //子进程进行写入 
// void SubProcessWrite(int wfd)//往  [1] 写    
// {
//     //子进程向父进程发送信息： 
//     std::string message = "father, I am your son prcess!";
//     while(true)
//     {
//         std::string info = message + getOtherMessage(); //子进程发给父进程的消息
//         write(wfd, info.c_str(), info.size());   
//         //写入管道的时候，没有写入\0，因为没有必要。(字符串以\0结尾，是C语言的规定，与文件无关)
        
//         sleep(1);   //让子进程写慢一点，方便我们观察
//     }
// }

// //子进程一直写  
// void SubProcessWrite(int wfd)//往  [1] 写    
// {
//     int pipesize = 0;
//     char c = 'A';
//     std::string message = "father, I am your son prcess!";
//     while(true)
//     {
//         write(wfd, &c, 1);   
//         std::cout << "pipesize: " << ++pipesize << std::endl;
//         sleep(1);           //子进程写慢一点
//     }
// }

//————————————————————————————————————————————————————————————————————————————————

//// 2.管道被写满时 && read fd 不读走并且没有关闭，写进程会被阻塞(此时写条件不具备) -- wait --等待写条件具备，等数据被读走后，管道才能继续写入

// //子进程只读一个就返回
// void SubProcessWrite(int wfd)//往  [1] 写    
// {
//     int pipesize = 0;
//     std::string message = "father, I am your son prcess!";
//     while(true)
//     {
//         char c = 'A';
//         write(wfd, &c, 1);   
//         std::cout << "pipesize: " << ++pipesize << std::endl;
//         break;
//     }
//     std::cout << "child quit ... " << std::endl;
// }
// //父进程进行读取，子进程写一条信息就退，父进程得到的返回值：
// void FatherProcessRead(int rfd)   //从 [0] 读    
// {
//     char inbuffer[size];          //变长数组
//     while(true)
//     {
//         sleep(1);
//         // 管道写满了就不动了：pipesize: 65536 
//         // 65536 / 1024 = 64 kb (管道的大小与系统有关)  本系统ubuntu 20.4 管道的大小是64 kb
//         ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);  //写入管道的时候，没有写入\0，
//         if(n > 0)
//         {
//             inbuffer[n] = 0;    // == '\0'  给字符串手动添加 \0 结尾，因为文件中的字符串末尾没有以 \0 结尾
//             std::cout << "father get message: " << inbuffer << std::endl; 
//         }
//        std::cout << "father get return val: " << n << std::endl;    //子进程写一条就退，父进程得到的返回值： 
//     }
// }

//————————————————————————————————————————————————————————————————————————————————

// // 3.管道一直在读 && 写端关闭了 wfd, 读端 read 返回值会读到 0 ，表示读到了文件结尾。
// //子进程进行写入
// void SubProcessWrite(int wfd)//往  [1] 写    
// {
//     int pipesize = 0;
//     char c = 'A';
//     std::string message = "father, I am your son prcess!";
//     while(true)
//     {
//         write(wfd, &c, 1);   
//         std::cout << "pipesize: " << ++pipesize << "write charator is: " << c++ << std::endl;
//         if(c == 'E') break;
        
//         sleep(1);
//     }
//     std::cout << "child quit ... " << std::endl;
// }

// //测试：4    //子进程一直写
// void SubProcessWrite(int wfd)//往  [1] 写    
// {
//     std::string message = "father, I am your son prcess!";
//     while(true)
//     {
//         std::string info = message + getOtherMessage(); //子进程发给父进程的消息
//         write(wfd, info.c_str(), info.size());
        
//         sleep(1);   //让子进程写慢一点，方便我们观察
//     }
// }

// //父进程进行读取
// void FatherProcessRead(int rfd)   //从 [0] 读    
// {
//     char inbuffer[size];          //变长数组
//     while(true)
//     {
//         ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);  //写入管道的时候，没有写入\0，
//         if(n > 0)
//         {
//             inbuffer[n] = 0;    // == '\0'  给字符串手动添加 \0 结尾，因为文件中的字符串末尾没有以 \0 结尾
//             std::cout << "father get message: " << inbuffer << std::endl; 
//         }
//         else if(n == 0)
//         {
//             //如果 read 的返回值为 0， 表示写端直接关闭了，我们此时读到了文件的结尾。
//             std::cout << "client quit, father get return val: " << n << "father quit too!" <<std::endl;
//             break;
//         }
//         else                    //读取错误
//         {
//             std::cerr << "read error" << std::endl;
//             break;
//         }

//         //测试4：父进程读一次，退出
//         sleep(1);
//         break;
//     }
// }

//————————————————————————————————————————————————————————————————————————————————

// 管道的特征4:  向两个窗口 写入不同的数据： 写入的数据 与 标准错误的数据
void SubProcessWrite(int wfd)//往  [1] 写    
{
    std::string message = "father, I am your son prcess!";
    while(true)
    {
        //准错误打印分隔符
        std::cerr << "+++++++++++++++++++++++++++++++++++++++++++" << std::endl;
        std::string info = message + getOtherMessage(); //子进程发给父进程的消息
        write(wfd, info.c_str(), info.size());
        std::cerr << info << std::endl;                 //向标准错误打印
    }
}
//父进程进行读取
void FatherProcessRead(int rfd)   //从 [0] 读    
{
    // 管道的特征4
    char inbuffer[size];          //变长数组
    while(true)
    {
        sleep(2);
        std::cout << "--------------------------------------------" << std::endl;

        ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);  //写入管道的时候，没有写入\0，
        if(n > 0)
        {
            inbuffer[n] = 0;    // == '\0'  给字符串手动添加 \0 结尾，因为文件中的字符串末尾没有以 \0 结尾
            std::cout << inbuffer << std::endl;         //向标输出 打印
        }
        else if(n == 0)
        {
            //如果 read 的返回值为 0， 表示写端直接关闭了，我们此时读到了文件的结尾。
            std::cout << "client quit, father get return val: " << n << "father quit too!" <<std::endl;
            break;
        }
        else                    //读取错误
        {
            std::cerr << "read error" << std::endl;
            break;
        }
    }
}

//————————————————————————————————————————————————————————————————————————————————

// //父进程进行读取
// void FatherProcessRead(int rfd)   //从 [0] 读    
// {
//     char inbuffer[size];          //变长数组
//     while(true)
//     {
//         sleep(500); //测试2. 子进程一直写，但父进程不读。会发生什么？  
//         // 管道写满了就不动了：pipesize: 65536 
//         // 65536 / 1024 = 64 kb (管道的大小与系统有关)  本系统ubuntu 20.4 管道的大小是64 kb
//         ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);  //写入管道的时候，没有写入\0，
//         if(n > 0)
//         {
//             inbuffer[n] = 0;    // == '\0'  给字符串手动添加 \0 结尾，因为文件中的字符串末尾没有以 \0 结尾
//             std::cout << "father get message: " << inbuffer << std::endl; 
//         }
        
//     }
// }

//————————————————————————————————————————————————————————————————————————————————

int main()
{
    //close(0);       //如果关闭0， 再打开的流 就是从 0 (最小的)开始
    // 1. 创建管道
    int pipefd[2];
    int n = pipe(pipefd);   //输出型参数，rfd, wfd 
    if(n != 0)
    {
        //输出标准错误
        std::cerr << "errno: " << errno << ": " << "errstring: " << strerror(errno) << std::endl;
        return 1;
    }
    //注意：系统默认打开（0,1,2）三个标准流，接下来打开的是 3...
    std::cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << std::endl;
    sleep(1);

    //2.创建子进程
    pid_t id = fork();
    if(id == 0)
    {
        std::cout << "子进程关闭不需要的fd(文件描述符)，准备发消息给父进程" << std::endl;
        sleep(1);
        //子进程  -- write
    //3.关闭不需要的 fd(文件描述符)
        close(pipefd[0]);   //关闭 读

        //创建孙子进程
        //if(fork() > 0) exit(0);  //孙子进程也是可以与它的爷爷进程进(父进程)行通信的。
        SubProcessWrite(pipefd[1]);//往  [1] 写    

        //使用完后，关闭
        close(pipefd[0]);   //关闭 读
        exit(0);
    }

    std::cout << "父进程关闭不需要的fd(文件描述符)，准备收消息了" << std::endl;
    sleep(1);
    //父进程  -- read
    //3.关闭不需要的 fd(文件描述符)
    close(pipefd[1]);        //关闭 写
    FatherProcessRead(pipefd[0]);   //从 [0] 读    

    //close(pipefd[0]);   //关闭 读
    // //让子进程进行等待，否则会有僵尸进程
    // // （子进程进行等待， 等的退出码， 等待方式：以阻塞方式等待）
    // pid_t rid = waitpid(id, nullptr, 0);
    // if(rid > 0)         //如果等待成功，输出一段提醒
    // {
    //     std::cout << "wait child process done" << std::endl;
    // }

//————————————————————————————————————————————————————————————————————————————————

    //验证 4：rfd 直接关闭，写端 wfd 一直在进行写入。
    std::cout << "5s, father close rfd" << std::endl;
    sleep(5);
    close(pipefd[0]);   //关闭 读

    int status = 0;
    //获取退出码 status
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)         //如果等待成功，输出一段提醒
    {
        // status 是一个位图结构，低 7 位，是我们当前进程收到的信号。
        std::cout << "wait child process done， exit sig: " << (status&0x7F) << std::endl;
        //次 第8位 是退出码
        std::cout << "wait child process done， exit code(ign): " << ((status>>8)&0xFF) << std::endl;
    }

//————————————————————————————————————————————————————————————————————————————————

    return 0;
}


//进程间通信（IPC） --- 进程具有独立性，进程间通信是有成本的。

// 1. 创建管道 测试结果：
// [a@192 pipe]$ ./testpipe 
// pipefd[0]: 3, pipefd[1]: 4

//close(0)； 后
// pipefd[0]: 0, pipefd[1]: 3


// 命令：【ps axj】 显示进程的详细信息（ajx 顺序可以乱）
// 命令：【 grep 】 查找指定进程， 【grep -v grep】 不显示grep

// 【运行】：
// [a@192 pipe2]$ while :; do ps axj | head -1 && ps axj | grep testpipe | grep -v grep; echo "--------------------------"; sleep 1; done 
//   PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
// --------------------------
//   PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
// --------------------------
//   ... ...
// --------------------------
//   PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
//   3498   4786   4786   3498 pts/0      4786 S+    1000   0:00 ./testpipe
// --------------------------
//   PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
//   3498   4786   4786   3498 pts/0      4786 S+    1000   0:00 ./testpipe
//   4786   4793   4786   3498 pts/0      4786 S+    1000   0:00 ./testpipe
// --------------------------
//   PPID    PID   PGID    SID TTY       TPGID STAT   UID   TIME COMMAND
//   3498   4786   4786   3498 pts/0      4786 S+    1000   0:00 ./testpipe
//   4786   4793   4786   3498 pts/0      4786 S+    1000   0:00 ./testpipe
// --------------------------
//   ... ...


// 【运行结果】：
// Makefile  testpipe  testpipe.cc
// [a@192 pipe2]$ ./testpipe 
// pipefd[0]: 3, pipefd[1]: 4
// 子进程关闭不要的fd(文件描述符)，准备发消息给父进程
// 父进程关闭不需要的fd(文件描述符)，准备收消息了
// father get message: father, I am your son prcess!messageid: 0  my pid is: 4793
// father get message: father, I am your son prcess!messageid: 1  my pid is: 4793
// father get message: father, I am your son prcess!messageid: 2  my pid is: 4793
// father get message: father, I am your son prcess!messageid: 3  my pid is: 4793
//   ... ...


//————————————————————————————————————————————————————————————————————————————————


//————————————————————————————————————————————————————————————————————————————————


// 代码验证：

// // 【管道的4种情况】：  
// 读 <==> 【 管道 】 <==>  写  //读写端变化的 4 种状态
// 1.如果管道内部是空的 && write fd 没有关闭，读取条件不具备时，读进程会阻塞 -- wait -> 等读条件具备 <- 等写入数据。
// 2.管道被写满时 && read fd 不读走并且没有关闭，写进程会被阻塞(此时写条件不具备) -- wait --等待写条件具备，等数据被读走后，管道才能继续写入
// 3.管道一直在读 && 写端关闭了 wfd, 读端 read 返回值会读到 0 ，表示读到了文件结尾。
// 4.rfd 直接关闭，写端 wfd 一直在进行写入。写端进程会被操作系统直接使用 13号信号关掉，相当于进程出现异常了。

// 没人读取数据 == 做无用功（OS 会杀掉对应的进程） 并给目标发送异常信号：13) SIGPIPE 
// 13号 信号 SIGPIPE：信号 内核会向写进程发管道断裂的信号(SIGPIPE),

// // 【管道的5种特征】：
// 1.匿名管道： 
// 只用来进行 具有继承关系的 类似父子孙 进程之间通信，(因为子进程会继承父进程的相关内核数据结构(拷贝父进程的文件描述符表))

// 父进程，为什么要等待子进程写入呢？可能会存在被多个进程同时访问的情况，导致数据不一致的问题。
// 2.管道内部，自带进程之间的同步机制 (比如父进程等待子进程输入结束)
// 同步：多执行流，执行代码的时候，具有明显的顺序性。
// 3.管道也是文件，文件的声明周期是随进程的。(进程结束了，打开的文件没关，该文件也会被释放掉)
// 4.管道文件在通信的时候，是面向字节流的。 write 的次数和读取的次数不是 1对1 匹配的。（多次写入，1次读取）
// 5.管道的通信模式，是一种特殊的 [半双工] 模式。 (我给你发消息时，你 不能 给我发消息)。
// 对应的还有一种 [全双工] (我给你发消息的同时，你也 能 给我也发消息)。

// 1次 写入信息 < PIPE_BUF 一般 < 512 字节(byte)是安全的 
//Linux 中 单次写入 < 4096 bytes 就是安全的。

//————————————————————————————————————————————————————————————————————————————————


//————————————————————————————————————————————————————————————————————————————————


//父进程进行读取，子进程写一条信息就退，父进程得到的返回值： 
// [a@192 pipe2]$ ./testpipe 
// pipefd[0]: 3, pipefd[1]: 4
// 子进程关闭不要的fd(文件描述符)，准备发消息给父进程
// 父进程关闭不需要的fd(文件描述符)，准备收消息了
// pipesize: 1
// child quit ... 
// father get message: A
// father get return val: 1
// father get return val: 0
// father get return val: 0
// father get return val: 0
//   ... ...         val: 0

//————————————————————————————————————————————————————————————————————————————————

// 3.管道一直在读 && 写端关闭了 wfd, 读端 read 返回值会读到 0 ，表示读到了文件结尾。
// 子进程每次写入不同字符，写完后关闭，父进程读到了 0， 表示写端直接关闭了，我们此时读到了文件的结尾。
// [a@192 pipe2]$ ./testpipe 
// pipefd[0]: 3, pipefd[1]: 4
// 子进程关闭不要的fd(文件描述符)，准备发消息给父进程
// 父进程关闭不需要的fd(文件描述符)，准备收消息了
// pipesize: 1write charator is: A
// father get message: A
// pipesize: 2write charator is: B
// father get message: B
// pipesize: 3write charator is: C
// father get message: C
// pipesize: 4write charator is: D
// child quit ... 
// father get message: D
// client quit, father get return val: 0father quit too!
// wait child process done

//————————————————————————————————————————————————————————————————————————————————

// 4.rfd 直接关闭，写端 wfd 一直在进行写入。
// [a@192 pipe2]$ ./testpipe 
// pipefd[0]: 3, pipefd[1]: 4
// 子进程关闭不要的fd(文件描述符)，准备发消息给父进程
// 父进程关闭不需要的fd(文件描述符)，准备收消息了
// father get message: father, I am your son prcess!messageid: 0  my pid is: 3976
// 5s, father close rfd
// wait child process done, exit sig: 13
// wait child process done, exit code(ign): 0

//————————————————————————————————————————————————————————————————————————————————

// 管道的特征4：

// 标准输出默认是向当前显示器打印的 0， 命令：ls /dev/pts 当前文件
// [a@192 pipe2]$ ls /dev/pts
// 0  ptmx
// [a@192 pipe2]$ ls /dev/pts   //打开一个新窗口，1 号文件
// 0  1  ptmx

// 当前窗口 0：
// [a@192 pipe2]$ ./testpipe 2>/dev/pts/1
// pipefd[0]: 3, pipefd[1]: 4
// 子进程关闭不要的fd(文件描述符)，准备发消息给父进程
// 父进程关闭不需要的fd(文件描述符)，准备收消息了
//--------------------------------------------
// father, I am your son prcess!messageid: 12908  my pid is: 4956
// father, I am your son prcess!messageid: 12909  my pid is: 4956
//   ... ...
//--------------------------------------------
// 再次读取的位置不一定 在数据的起始位置
// essageid: 17  my pid is: 5977father, I am your son prcess!messageid: 18  my pid is: 5977
// father, I am your son prcess!messageid: 19  my pid
//   ... ...
//--------------------------------------------

// 新窗口cerr 1： 
// [a@192 ~]$ father, I am your son prcess!messageid: 0  my pid is: 4634
// +++++++++++++++++++++++++++++++++++++++++++
// father, I am your son prcess!messageid: 1  my pid is: 4634
// +++++++++++++++++++++++++++++++++++++++++++
// father, I am your son prcess!messageid: 2  my pid is: 4634
// +++++++++++++++++++++++++++++++++++++++++++
// father, I am your son prcess!messageid: 3  my pid is: 4634
//   ... ...

//————————————————————————————————————————————————————————————————————————————————
